String et stream ✱ ****************** Bien que beaucoup d'efforts aient été faits pour améliorer le support des caractères internationaux en C++, cela reste encore fastidieux. Nous choisissons donc dans cette unité de se limiter aux caractères de la table `ASCII de 32 à 126 `_, c'est à dire : * Les chiffres et les lettres majuscules/minuscules * L'espace * Les signes de ponctuation : ! ? . ; , * Les signes mathématiques : + - / * % < > = * Les signes de dev : ( ) [ ] {} & # \" ~ * Et quelques autres : $ _ - @ ^ .. warning:: Par exemple, vous ne pouvez donc pas utiliser les accents : é à è ê et le ç. En effet, ces caractères nécessitent un encodage sur 2 octets ou plus. Ainsi, si vous demandez la longueur de la chaîne, la fonction retournera en réalité le nombre d'octets utilisés et ce résultat ne correspondra pas aux nombres de caractères effectivement présents dans la chaîne. Les littéraux ============= Dans vos programmes vous allez écrire des littéraux sous la forme suivante : .. code-block:: cpp "Bonjour" Cette syntaxe, datant du langage C, correspond à la création d'un tableau de caractères char[]. Il est possible d'y insérer des caractères spéciaux en utilisant un backslash *\\* : * \\n : retour à la ligne * \\t : tabulation * \\\\ : un backslash (pour les chemins sur le disque) Le type string ============== La librairie standard du C++ (std) intègre le type **string** pour représenter des objets chaînes de caractères. Pour cela, il faut insérer en début de votre code la ligne suivante : .. code-block:: cpp #include Pourquoi le langage fournit un objet string alors que les chaînes de caractères datant du C semblent largement suffire : * Un objet string peut changer de taille dynamiquement en insérant ou retirant des caractères. Les tableaux de char sont de taille fixe. * Les chemins de fichier ne sont plus limités en taille ! Sans utiliser un string, on doit alors allouer un tableau de taille fixe pour stocker un chemin de fichier, une belle source de bugs ! * De nombreuses fonctions sont disponibles à travers l'objet string ce qui facilite l'écriture et la relecture du code. * On peut utiliser des opérateurs sur les string comme le + pour fusionner deux chaînes. Syntaxe ======= Le classe string est une spécialisation de la classe modèle **std::basic_string** pour le type *char*. On peut trouver toutes les spécifications de cette classe modèle sur `cette page `_. Voici une liste des commandes intéressantes : .. csv-table:: Opérations sur les strings :header: "Type", "Exemple", "" :widths: 10, 10, 10 Affichage, std::cout << s1 << std::endl; , Initialisation, std::string s1 = \"Exemple 1\"; , Initialisation, std::string s2(\"Exemple 2\"); , Initialisation, std::string t {\"Hello world\"}; , Conversion int->str, std::string s = std::to_string(42); , Conversion str->int, std::stoi(s);, Conversion str->double, std::stod(s);, Conversion str->float, std::stof(s);, Méthode, t.clear(), Efface tous les caractères Méthode, t.erase(i٬nb), Retire *nb* caractères à partir de l'index *i* Méthode, t.insert(i٬\"1-\"), Insère une chaîne dans la chaîne *t* à la position *i* Méthode, t.erase(index٬nb), Retire *nb* caractères à la position *i* Méthode, t.replace(index٬nb٬\"test\"), Remplace les *nb* caractères à la position *i* Méthode, t.append(s1), Insère en fin Méthode const, t.length(), Nombre de caractères Méthode const, t.find(\"Ex\"), Retourne l'index de la première occurrence Méthode const, t.rfind(\"Ex\"), Retourne l'index de la dernière occurrence Méthode const, t.substr(i٬nb), Retourne une sous-chaîne depuis l'index *i* Opérateur +, s1+\"@\"+s2, Concaténation Opérateur +=, s1+= \"fin\", Insère en fin Opérateur [], s1[i], retour le i-ème caractère Opérateur <, s1 < s2, Comparaison lexicographique Opérateur ==, s1 == s2, Comparaison des caractères Test, t.empty(), Indique si la chaîne est vide Test, t.starts_with(\"Ex\"), Teste le début de la chaîne de caractères Test, t.ends_with(\"1\"), Teste la fin de la chaîne de caractères .. quiz:: QuString :title: Le type string Indiquez si les affirmations suivantes sont vraies ou fausses : * :quiz:`{"type":"TF","answer":"T"}` L'écriture \"3\" correspond à un littéral. * :quiz:`{"type":"TF","answer":"F"}` Pour créer un string, une seule syntaxe est possible. * :quiz:`{"type":"TF","answer":"F"}` Le type string est un type fondamental du C++. * :quiz:`{"type":"TF","answer":"F"}` On peut concaténer un string avec un int. * :quiz:`{"type":"TF","answer":"F"}` L'écriture : string s = 42 convertit implicitement un int en string. * :quiz:`{"type":"TF","answer":"F"}` La fonction stod() convertit un double en string. * :quiz:`{"type":"TF","answer":"T"}` La fonction to_string() permet de convertir un numérique en string. .. quiz:: QuString2 :title: Opérations sur les string Les variables s1 et s2 représentent deux string initialisés avec \"BOB\" et \"EVA\". Pour chacune des expressions, donnez le résultat **SANS** guillemets s'il existe ou indiquez ERR : .. csv-table:: :header: Expression, Résultat :widths: 10, 10 :delim: ! \"Hello\" + 4 ! :quiz:`{"type":"FB","answer":"ERR"}` s1 + \"_\" + s2 ! :quiz:`{"type":"FB","answer":"BOB_EVA"}` s1 + 5 ! :quiz:`{"type":"FB","answer":"ERR"}` to_string(50) ! :quiz:`{"type":"FB","answer":"50"}` Affichage ========= iostream -------- Cette librairie propose un objet **cout** permettant d'effectuer des sorties sur la console de manière plus intuitive que la fonction *printf*. Pour cela, l'opérateur **<<** a été redéfini pour traiter tous les types fondamentaux. Pour les booléens cependant, il affiche 0 pour *false* et 1 pour *true*. La librairie inclut aussi des constantes comme **endl** pour effectuer un retour à la ligne. Nous pouvons ainsi écrire : .. code-block:: cpp #include int main() { int a = 17; string t = "Bonjour"; std::cout << a << std::endl << t << std::endl; } .. note:: Vous pouvez retirer les std en écrivant : using namespace std; En chaînant plusieurs affichages à la suite, les éléments sont accolés, ainsi *cout << 18 << 13* affiche 1813. Il faut alors penser à insérer des espacements supplémentaires pour rendre l'affichage lisible : .. code-block:: cpp cout << "Coordonnées : " << x << " " << y << endl; Formatage des nombres --------------------- La librairie fournit une fonction *setprecision()* permettant de contrôler l'affichage des nombres flottants : .. code-block:: cpp #include #include using namespace std; int main() { cout << setprecision(5) << 3.141519 << endl; // ==>> 3.1415 5 chiffres cout << setprecision(3) << 3.141519 << endl; // ==>> 3.14 3 chiffres cout << setfill('-'); cout << setw(5) << 25 << endl; // ---25 cout << setw(5) << 148; // --148 } Surcharger l'opérateur << pour vos objets ----------------------------------------- Pour les structures, on peut surcharger l'opérateur << pour qu'il sache afficher vos objets. Ainsi, vous fixez un format d'affichage identique pour tous les objets de même type. Voici un exemple dans le code ci-dessous : .. code-block:: cpp #include using namespace std; struct Point { int x,y; Point(int xx,int yy) { x = xx; y = yy; } }; ostream & operator << (ostream & stream, const Point & P) { stream << "(" << P.x << "," << P.y << ")"; return stream; } int main() { Point P(4,5); cout << P; } >> (4,5) Stream ====== Rappel : le polymorphisme permet à des objets d'une même hiérarchie de classes de partager une interface identique. Ainsi, bien que les instances soient de natures différentes, elles vont se comporter de manière similaire. Output stream ------------- Dans la hiérarchie des output streams, on trouve : * std::cout : instance de la classe mère ostream (output stream) * ostringstream : classe fille dédiée aux sorties dans une chaîne de caractères (output string stream) * ofstream : classe fille dédiée aux sorties dans un fichier (output file stream) .. image:: ostream.png :align: center :scale: 60% Exemple ------- Nous avons vu les fonctionnalités disponibles à travers l'objet *cout* pour l'affichage à l'écran. Ainsi, toutes les instances appartenant à la même hiérarchie disposent des même facilités ! .. code-block:: cpp #include #include #include void ExPolymorphisme(std::ostream & s, int i) { s << "File_" << i << ".txt" << std::endl; } int main() { // sortie à l'écran ExPolymorphisme(std::cout,7); // sortie dans un string buffer std::ostringstream os; ExPolymorphisme(os, 7); std::cout << os.str(); // sortie dans un fichier std::ofstream ofs("C:\\test\\output.txt"); ExPolymorphisme(ofs, 7); ofs.close(); return 0; } >> Affichage : File_7.txt >> String : File_7.txt .. image:: file.png :align: center :scale: 60% Input stream ------------ La logique est identique pour les inputs streams ; une classe mère *istream* dipose d'une instance *std::cin* ainsi que deux classes filles : * *ifstream* spécialisée dans la lecture des fichiers (input file stream) * *istringstream* spécialisée dans la lecture des chaînes de caractères (input string stream) .. image:: in.png :align: center :scale: 60% Voici un programme d'exemple gérant les trois types d'entrée : * Le clavier avec std::cin, l'utilisateur va entrer : 11 22 33 + Enter * Un objet inputstream initialisé avec la chaîne : \"11 22 33\" * Un fichier dont le contenu est affiché ci-dessous .. code-block:: cpp #include #include #include void ExPolymorphisme(std::istream & is) { int i; is >> i; std::cout << "word : " << i << std::endl; } int main() { // lecture au clavier ExPolymorphisme(std::cin); // lecture dans une chaîne std::istringstream iss("11 12 13"); ExPolymorphisme(iss); // lecture dans un fichier std::ifstream ifs("C:\\test\\output.txt"); ExPolymorphisme(ifs); ifs.close(); return 0; } .. image:: out.png :align: center :scale: 60% Dans les trois cas, l'opérateur >> lit le premier nombre disponible sur le flux d'entrée c'est à dire le 11. .. note:: Le caractère espace sert de séparateur entre les différentes informations. .. warning:: Tout n'est pas aussi rose côté polymorphisme. En effet, si dans cet exemple tout fonctionne à merveille, il reste des zones d'ombre difficilement homogénéisables. Par exemple, lorsqu'il s'agit de détecter la fin du flux, pour un fichier, cette notion est évidente, mais pour l'entrée clavier... il n'y a pas de notion de fin ! Il faudra donc gérer ce genre de détails au cas par cas. Dans les fonctions utiles, on peut citer : * std::getline(streamin,str) : lit une ligne et la charge dans l'objet string str * std::getline(streamin,str, \"#\") : idem mais en utilisant le caractère # comme séparateur